//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.RuntimeServices.Common
{
    using System;
    using System.Collections.ObjectModel;
    using System.Configuration;
    using System.Linq;
    using System.Reflection;
    using System.Text;    
    using System.Workflow.ComponentModel;
    using System.Workflow.Runtime;
    using System.Workflow.Runtime.Tracking;
    using Microsoft.Research.eResearch.Common.Linq;
    using Microsoft.Research.eResearch.Common;
    using Microsoft.Research.ScientificWorkflow;

    public static class WorkflowRuntimeHelper
    {
        private static Collection<ActivityExecutionStatus> allActivityExecutionStatus = new Collection<ActivityExecutionStatus>()
        {
            ActivityExecutionStatus.Canceling, 
            ActivityExecutionStatus.Closed, 
            ActivityExecutionStatus.Compensating,
            ActivityExecutionStatus.Executing,
            ActivityExecutionStatus.Faulting,
            ActivityExecutionStatus.Initialized
        };

        private static Collection<TrackingWorkflowEvent> allTrackingWorkflowEvents = new Collection<TrackingWorkflowEvent>()
        {
            TrackingWorkflowEvent.Aborted, 
            TrackingWorkflowEvent.Changed, 
            TrackingWorkflowEvent.Completed,
            TrackingWorkflowEvent.Created, 
            TrackingWorkflowEvent.Exception, 
            TrackingWorkflowEvent.Idle,
            TrackingWorkflowEvent.Loaded, 
            TrackingWorkflowEvent.Persisted, 
            TrackingWorkflowEvent.Resumed,
            TrackingWorkflowEvent.Started, 
            TrackingWorkflowEvent.Suspended, 
            TrackingWorkflowEvent.Terminated,
            TrackingWorkflowEvent.Unloaded
        };

        private static Collection<ActivityExecutionStatus> activityClosedExecutionStatus = new Collection<ActivityExecutionStatus>()
        {
            ActivityExecutionStatus.Closed
        };

        /// <summary>
        /// Qualified Name of the ForActivity
        /// </summary>
        private static string forActivityQualifiedName = "TridentBasicActivities.Activities.ForActivity";

        /// <summary>
        /// Qualified Name of the Replicator Activity
        /// </summary>
        private static string replicatorActivityQualifiedName = "System.Workflow.Activities.ReplicatorActivity";

        public static Collection<ActivityExecutionStatus> AllActivityExecutionStatus
        {
            get
            {
                return allActivityExecutionStatus;
            }
        }

        public static Collection<TrackingWorkflowEvent> AllTrackingWorkflowEvents
        {
            get
            {
                return allTrackingWorkflowEvents;
            }
        }


        public static Collection<ActivityExecutionStatus> ActivityClosedExecutionStatus
        {
            get
            {
                return activityClosedExecutionStatus;
            }
        }        

        /// <summary>
        /// HACK: Uses reflection to grab a reference to the PRIVATE root activity inside the WorkflowRuntime.
        /// </summary>
        /// <param name="runtime">The workflow runtime.</param>
        /// <param name="instanceId">The workflow instance identifier.</param>
        /// <returns></returns>
        public static Activity GrabRootActivity(WorkflowRuntime runtime, Guid instanceId)
        {
            object workflowExecutors = GrabPrivateField(runtime, "workflowExecutors");
            object workflowExecutorDictionary = InvokePrivateMethod(workflowExecutors, "get_Item", instanceId);
            object workflowExecutor = InvokePrivateMethod(workflowExecutorDictionary, "get_Item", instanceId);
            object rootActivity = GrabPrivateField(workflowExecutor, "rootActivity");
            return (Activity)rootActivity;
        }

        /// <summary>
        /// Populates the tracking profile with data extracts for every property in the activity tree.
        /// </summary>
        /// <param name="activity">The activity tree to reflect over.</param>
        /// <param name="profile">The tracking profile which will have the data extracts added.</param>
        public static void PopulateTrackingProfileExtracts(Activity activity, TrackingProfile profile)
        {
            ExceptionHandler.Handle(
              PolicyName.LoggingPolicy,
              () =>
              {
                  // Stop the recursion if we are at the root activity (whose parent is null)
                  if (activity == null)
                  {
                      return;
                  }

                  // Create a track point for the activity's type and listen for all events
                  ActivityTrackPoint activityTrackPoint = new ActivityTrackPoint();
                  ActivityTrackingLocation location = new ActivityTrackingLocation(activity.GetType(), false, WorkflowRuntimeHelper.AllActivityExecutionStatus);
                  ActivityTrackingCondition condition = new ActivityTrackingCondition("Name", activity.Name);
                  condition.Operator = ComparisonOperator.Equals;
                  location.Conditions.Add(condition);
                  activityTrackPoint.MatchingLocations.Add(location);

                  //// To get the Name attribute associated with the activity
                  NameAttribute[] nameAttributes = activity.GetType().GetCustomAttributes(typeof(ScientificWorkflow.NameAttribute), false) as NameAttribute[];
                  if (nameAttributes.Length != 0)
                  {
                      activityTrackPoint.Annotations.Add(nameAttributes[0].Label);
                  }
                  else
                  {
                      activityTrackPoint.Annotations.Add(activity.GetType().Name);
                  }

                  // Get a list of all built-in properties for the Activity class
                  var activityProperties = typeof(Activity).GetProperties()
                      .Select(pi => pi.Name);
                  // Get a list of all built-in properties for the CompositeActivity class
                  var compositeActivityProperties = typeof(CompositeActivity).GetProperties()
                      .Select(pi => pi.Name);

                  // Get the properties of the current activity that aren't in Activity or CompositeActivity and add their name as a data extract
                  activity.GetType().GetProperties()
                      .Where(pi => !activityProperties.Contains(pi.Name))
                      .Where(pi => !compositeActivityProperties.Contains(pi.Name))
                      .Select(pi =>
                      {
                          ActivityDataTrackingExtract propExtract = new ActivityDataTrackingExtract(pi.Name);
                          if (IsInputProperty(pi))
                          {
                              propExtract.Annotations.Add("Input");
                          }
                          else if (IsOutputProperty(pi))
                          {
                              propExtract.Annotations.Add("Output");
                          }
                          else if (IsInOutProperty(pi))
                          {
                              propExtract.Annotations.Add("InOut");
                          }
                          else
                          {
                              propExtract.Annotations.Add("NA");
                          }

                          propExtract.Annotations.Add(pi.PropertyType.FullName);
                          return propExtract;
                      })
                      .ForEach(extract => activityTrackPoint.Extracts.Add(extract));

                  profile.ActivityTrackPoints.Add(activityTrackPoint);

                  // Add a user trackpoint to track the user events.
                  UserTrackPoint userTrackPoint = new UserTrackPoint();
                  UserTrackingLocation userLocation = new UserTrackingLocation();
                  userLocation.ActivityType = activity.GetType();
                  userLocation.ActivityTypeName = activity.Name;
                  userLocation.MatchDerivedActivityTypes = false;
                  userLocation.ArgumentType = typeof(object);
                  userLocation.MatchDerivedArgumentTypes = true;
                  userTrackPoint.MatchingLocations.Add(userLocation);

                  if (nameAttributes.Length != 0)
                  {
                      userTrackPoint.Annotations.Add(nameAttributes[0].Label);
                  }
                  else
                  {
                      activityTrackPoint.Annotations.Add(activity.GetType().Name);
                  }

                  profile.UserTrackPoints.Add(userTrackPoint);

                  // If the activity has children, recurse for each child activity
                  CompositeActivity composite = activity as CompositeActivity;
                  if (composite != null)
                  {
                      foreach (Activity a in composite.EnabledActivities)
                      {
                          PopulateTrackingProfileExtracts(a, profile);
                      }
                  }
              });
        }

        /// <summary>
        /// Populates the tracking profile with data extracts for every output property in the activity tree.
        /// </summary>
        /// <param name="activity">The activity tree to reflect over.</param>
        /// <param name="profile">The tracking profile which will have the data extracts added.</param>
        public static void PopulateOutputTrackingExtracts(Activity activity, TrackingProfile profile)
        {
            ExceptionHandler.Handle(
               PolicyName.LoggingPolicy,
               () =>
               {
                   // Stop the recursion if we are at the root activity (whose parent is null)
                   if (activity == null)
                   {
                       return;
                   }

                   // Create a track point for the activity's type and listen for the Activity closed event
                   ActivityTrackPoint activityTrackPoint = new ActivityTrackPoint();
                   ActivityTrackingLocation location = new ActivityTrackingLocation(
                       activity.GetType(),
                       false,
                       WorkflowRuntimeHelper.ActivityClosedExecutionStatus);
                   ActivityTrackingCondition condition = new ActivityTrackingCondition("Name", activity.Name);
                   condition.Operator = ComparisonOperator.Equals;
                   location.Conditions.Add(condition);
                   activityTrackPoint.MatchingLocations.Add(location);

                   // To get the Name attribute associated with the activity
                   ScientificWorkflow.NameAttribute[] nameAttributes =
                       activity.GetType().GetCustomAttributes(typeof(ScientificWorkflow.NameAttribute), false) as ScientificWorkflow.NameAttribute[];
                   if (nameAttributes.Length != 0)
                   {
                       activityTrackPoint.Annotations.Add(nameAttributes[0].Label);
                   }

                   // Get all the output properties of the current activity 
                   activity.GetType().GetProperties()
                       .Where(pi => IsOutputProperty(pi) || IsInOutProperty(pi))
                       .Select(pi =>
                       {
                           ActivityDataTrackingExtract propExtract = new ActivityDataTrackingExtract(pi.Name);
                           propExtract.Annotations.Add("Output");
                           propExtract.Annotations.Add(pi.PropertyType.FullName);
                           return propExtract;
                       })
                       .ForEach(extract => activityTrackPoint.Extracts.Add(extract));

                   profile.ActivityTrackPoints.Add(activityTrackPoint);

                   // If the activity has children, recurse for each child activity
                   CompositeActivity composite = activity as CompositeActivity;
                   if (composite != null)
                   {
                       foreach (Activity a in composite.EnabledActivities)
                       {
                           PopulateOutputTrackingExtracts(a, profile);
                       }
                   }
               });
        }

        private static object GrabPrivateField(object input, string fieldName)
        {
            return input.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance).GetValue(input);
        }

        private static object InvokePrivateMethod(object input, string methodName, params object[] methodParameters)
        {
            return input.GetType().GetMethod(methodName).Invoke(input, methodParameters);
        } 

        private static bool IsInputProperty(PropertyInfo pi)
        {
            return ((pi.GetCustomAttributes(typeof(ScientificWorkflow.RequiredInputParamAttribute), false).Length != 0) ||
                (pi.GetCustomAttributes(typeof(ScientificWorkflow.OptionalInputParamAttribute), false).Length != 0) ||
                (pi.GetCustomAttributes(typeof(ScientificWorkflow.InputParamAttribute), false).Length != 0));
        }

        private static bool IsInOutProperty(PropertyInfo pi)
        {
            return ((pi.GetCustomAttributes(typeof(ScientificWorkflow.InOutParamAttribute), false).Length != 0) ||
                (pi.GetCustomAttributes(typeof(ScientificWorkflow.RequiredInOutParamAttribute), false).Length != 0) ||
                (pi.GetCustomAttributes(typeof(ScientificWorkflow.OptionalInOutParamAttribute), false).Length != 0));
        }

        private static bool IsOutputProperty(PropertyInfo pi)
        {
            return pi.GetCustomAttributes(typeof(ScientificWorkflow.OutputParamAttribute), false).Length != 0;
        }

       /// <summary>
       /// Fetches the unique Activity Parameter Key.
       /// </summary>
       /// <param name="activityName">The activity name.</param>
       /// <param name="parameterName">The parameter name.</param>
       /// <returns></returns>
        public static string FetchActivityParamUniqueKey(string activityName, string parameterName)
        {
            StringBuilder strBuilder = new StringBuilder();
            strBuilder.Append(activityName);
            strBuilder.Append("_");
            strBuilder.Append(parameterName);
            return strBuilder.ToString();
        }

        /// <summary>
        /// Fetches the unique Activity Iterator Key.
        /// </summary>
        /// <param name="activityParamKey">The Activity parameter key.</param>
        /// <returns></returns>
        public static string FetchActivityIteratorUniqueKey(string activityParamKey)
        {
            return activityParamKey + "_" + "iterator";
        }

        /// <summary>
        /// Used to determine whether the given activity sequence supports iteration.
        /// </summary>
        /// <param name="actSequence">The activity sequence.</param>
        /// <returns>Value indicating whether the activity supports iteration.</returns>
        public static bool IsIterator(DataLayer.ActivitySequence actSequence)
        {
            if (!String.IsNullOrEmpty(actSequence.Activity.ActivityClass))
            {
                if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["ForActivityQualifiedName"]))
                {
                    forActivityQualifiedName = ConfigurationManager.AppSettings["ForActivityQualifiedName"];
                }

                if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["ReplicatorActivityQualifiedName"]))
                {
                    replicatorActivityQualifiedName = ConfigurationManager.AppSettings["ReplicatorActivityQualifiedName"];
                }

                if (actSequence.Activity.ActivityClass.Equals(forActivityQualifiedName)
                            || actSequence.Activity.ActivityClass.Equals(replicatorActivityQualifiedName))
                {
                    return true;
                }
            }

            return false;
        }
    }
}
